package com.googlecode.androidkurskoblenz.ledcube;

import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;

import android.app.Activity;
import com.googlecode.androidkurskoblenz.sockethelper.ISocket;

public class CubeConnection
{
	private class ReaderThread extends Thread
	{
		public ReaderThread()
		{
			super("CubeConnection.ReaderThread");
		}
		
		private void onError()
		{
			mActivity.runOnUiThread(new Runnable()
			{
				@Override
				public void run()
				{
					CubeConnection.this.onError();
				}
			});
		}
		
		private void onReceive(final String data)
		{
			mActivity.runOnUiThread(new Runnable()
			{
				@Override
				public void run()
				{
					CubeConnection.this.onReceive(data);
				}
			});
		}
		
		@Override
		public void run()
		{
			InputStream stream;
			try
			{
				stream = mSocket.getInputStream();
				String data = "";
				byte[] buffer = new byte[1024];
				
				while(true)
				{
					stream.read(buffer);
					data += new String(buffer);
					if(0 == stream.available())
					{
						this.onReceive(data);
						data = "";
					}
				}
			}
			catch(IOException e)
			{
				this.onError();
			}
		}
	}
	
	public class ConnectionError extends Exception
	{
		private static final long serialVersionUID = 538971970842162541L;
	}
	
	public enum CubeStatus
	{
		Connected, Connecting, NotConnected
	}
	
	public class StatusException extends Exception
	{
		
		private static final long serialVersionUID = -2206026193041083735L;
		
		public StatusException()
		{
			super();
		}
		
		public StatusException(String detailMessage)
		{
			super(detailMessage);
		}
	}
	
	public static final UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
	
	private Activity mActivity;
	private ReaderThread mReaderThread;
	private ISocket mSocket;
	private CubeStatus mStatus = CubeStatus.NotConnected;
	private CubeInterface mTarget;
	protected byte mWidth, mDepth, mHeight;
	
	private void onError()
	{
		if(mStatus == CubeStatus.NotConnected)
			// Service has been stopped , don't throw any more errors
			return;
		
		if(mTarget != null)
			mTarget.onCubeError(this);
		
		if(mStatus == CubeStatus.Connecting)
			// Service is connecting, we can't stop here. The connecting thread will
			// call onError itself.
			return;
		
		try
		// StatusException will only be thrown if the Service is still connecting.
		{
			stop();
		}
		catch(StatusException e)
		{}
	}
	
	private void onReceive(String data)
	{
		if(mTarget != null)
			mTarget.onCubeReceive(this, data);
	}
	
	/*
	 * returns the depth of the cube (number of LEDs along the y axis)
	 */
	public byte getDepth()
	{
		return mDepth;
	}
	
	/*
	 * returns the height of the cube (number of LEDs along the z axis)
	 */
	public byte getHeight()
	{
		return mHeight;
	}
	
	/*
	 * returns the width of the cube (number of LEDs along the x axis
	 */
	public byte getWidth()
	{
		return mWidth;
	}
	
	/*
	 * returns the status of the connection (disconnected, connected, connecting)
	 */
	public CubeStatus isRunning()
	{
		return mStatus;
	}
	
	/*
	 * send data as a String to the cube. You may want to use setLED to set LEDs.
	 */
	public void send(String data) throws ConnectionError, StatusException
	{
		if(mStatus != CubeStatus.Connected)
			throw new StatusException();
		
		if(mSocket != null)
		{
			try
			{
				mSocket.getOutputStream().write(data.getBytes());
			}
			catch(IOException e)
			{
				onError();
				throw new ConnectionError();
			}
		}
	}
	
	/*
	 * set the LED at (x|y|z) to the specified value
	 */
	public void setLED(byte x, byte y, byte z, byte value) throws ConnectionError, StatusException
	{
		this.send(String.format("S%c%c%c%c\n", z, y, x, value));
	}
	
	/*
	 * start a connection to a cube
	 * @param activity The activity to use for running callbacks.
	 * @param callback The object to receive callbacks from the cube
	 * @param address An abstract socket to connect to
	 * cube
	 */
	public void start(Activity activity, CubeInterface callback, ISocket aSocket) throws StatusException
	{
		if(this.mStatus != CubeStatus.NotConnected)
			throw new StatusException();
		
		this.mActivity = activity;
		this.mTarget = callback;
		this.mSocket = aSocket;
		
		Thread connectThread = new Thread(new Runnable()
		{
			@Override
			public void run()
			{
				byte[] buffer = new byte[6];
				
				try
				{
					mSocket.connect();
					mSocket.getInputStream().read(buffer);
				}
				catch(IOException e)
				{
					mStatus = CubeStatus.Connected;// to make sure the target gets
																					// notified
					mActivity.runOnUiThread(new Runnable()
					{
						@Override
						public void run()
						{
							onError();
						}
					});
					return;
				}
				
				mWidth = buffer[0];
				mDepth = buffer[1];
				mHeight = buffer[2];
				
				mReaderThread = new ReaderThread();
				mReaderThread.start();
				
				mStatus = CubeStatus.Connected;
				if(mTarget != null)
				{
					mActivity.runOnUiThread(new Runnable()
					{
						@Override
						public void run()
						{
							mTarget.onCubeConnect(CubeConnection.this);
						}
					});
				}
			}
		});
		connectThread.start();
	}
	
	/*
	 * cancel the connection to the cube
	 */
	public void stop() throws StatusException
	{
		if(mStatus == CubeStatus.NotConnected)
			return;
		
		if(mStatus == CubeStatus.Connecting)
			throw new StatusException("Can't stop service while connecting!");
		
		this.mStatus = CubeStatus.NotConnected;
		
		try
		{
			if(mSocket != null)
				mSocket.close();
		}
		catch(IOException e)
		{}
		
		if(mTarget != null)
			mTarget.onCubeDisconnect(this);
	}
}
